// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <linux/tcp.h>

#include <utility>
#include "CaeTcpServerSocket.h"
#include "../cae_common/CaeLogConfig.h"
#include "../cae_agent/CaeAgent.h"
#include "../cae_socket/CaeTlsSocket.h"
#include "../cae_common/CaeMsgCode.h"

const int MAX_WAIT_QUEUE_LEN = 5;

void *CaeTcpAcceptTaskEntry(void *serverSocket)
{
    auto *tcpServerSocket = (CaeTcpServerSocket *)serverSocket;
    CaeSocket *clientSocket = nullptr;
    struct sockaddr_in strClient{};
    char serverIpAddr[INET_ADDRSTRLEN];
    char clientIpAddr[INET_ADDRSTRLEN];
    uint32_t remoteIp;
    uint16_t remotePort;
    uint32_t localIp;
    uint16_t localPort;
    int clientSockFd;
    int listenFd;
    socklen_t len;
    LOGD("CaeTcpAcceptTaskEntry run.");
    tcpServerSocket->GetAddress(&remoteIp, &remotePort, &localIp, &localPort);
    char threadName[32] = {0};
    int ret = snprintf_s(threadName, sizeof(threadName), sizeof(threadName) - 1, "%s" ,"TcpServerSocket");
    if (ret == -1) {
        LOGE("Failed to snprintf_s threadName.");
    }
    prctl(PR_SET_NAME, threadName);
    listenFd = (int) tcpServerSocket->GetFd();
    LOGD("Listen Fd: %d.", listenFd);
    len = sizeof(strClient);
    LOGD("Len: %d.", len);

    do {
        LOGD("CaeTcpAcceptTaskEntry waiting client.");
        try {
            clientSockFd = accept(listenFd, (struct sockaddr *)&strClient, &len);
        } catch (std::exception &e) {
            LOGE("Accept client error: %s", e.what());
        }

        remoteIp = htonl(strClient.sin_addr.s_addr);
        remotePort = ntohs(strClient.sin_port);
        int flag;
        flag = 1;
        setsockopt(clientSockFd, IPPROTO_TCP, TCP_NODELAY, (const char *)&flag, sizeof(flag));
        memset_s(clientIpAddr, INET_ADDRSTRLEN, '\0', INET_ADDRSTRLEN);
        if (inet_ntop(AF_INET, (void *)&(strClient.sin_addr.s_addr), clientIpAddr,
                      (socklen_t)INET_ADDRSTRLEN) != nullptr) {
            LOGI("The new client is (%s:%d)", clientIpAddr, remotePort);
        }
        if (CaeAgent::GetInstance().IsIpInBlacklist(clientIpAddr)) {
            usleep(100);
            LOGE("ip:%s in blacklist.", clientIpAddr);
            if (clientSockFd != -1) {
                close(clientSockFd);
            }
            continue;
        }
        if (clientSockFd == -1) {
            LOGE("The server failed to accept the client. Please try again. errno: (%d): %s", errno, strerror(errno));
            continue;
        }

        ServerType serverType = tcpServerSocket->GetServerType();
        if (serverType == ServerType::TLS || serverType == ServerType::WSS) {
            clientSocket = new (std::nothrow) CaeTlsSocket();
            clientSocket->SetRemoteIpStr(clientIpAddr);
            clientSocket->SetRemotePortStr(std::to_string(remotePort));
            clientSocket->SetServerType(serverType);
            clientSocket->SetFd(clientSockFd);
            clientSocket->SetStatus(1);
            clientSocket->SetAddress(remoteIp, remotePort, localIp, localPort);
            clientSocket->SetSocketTimeout(1, 0);
            if (!(dynamic_cast<CaeTlsSocket *>(clientSocket))->ConfigSSL()) {
                LOGE("config SSL connect failed!");
                delete clientSocket;
                clientSocket = nullptr;
                close(clientSockFd);
                continue;
            }
            LOGI("Start set new socket Client: %x", clientSockFd);
            tcpServerSocket->NotifyServerService(clientSocket);
            LOGD("CaeTcpAcceptTaskEntry %x", clientSockFd);
        } else {
            clientSocket = new (std::nothrow) CaeTcpSocket();
            clientSocket->SetRemoteIpStr(clientIpAddr);
            clientSocket->SetRemotePortStr(std::to_string(remotePort));
            clientSocket->SetServerType(serverType);
            clientSocket->SetFd(clientSockFd);
            clientSocket->SetStatus(1);
            clientSocket->SetAddress(remoteIp, remotePort, localIp, localPort);
            LOGI("Start set new socket Client: %x", clientSockFd);
            tcpServerSocket->NotifyServerService(clientSocket);
            LOGD("CaeTcpAcceptTaskEntry %x", clientSockFd);
        }
    } while (tcpServerSocket->GetListenStatus() == 1);
    LOGE("Tcp server(%s:%d) accept thread exit!", serverIpAddr, localPort);
    tcpServerSocket->SetStatus(-1);
    tcpServerSocket->EventNotice();
    tcpServerSocket->SetThreadId((pthread_t) (-1));
    if (clientSocket != nullptr) {
        delete clientSocket;
        clientSocket = nullptr;
    }
}

int CaeTcpServerSocket::CaeCreateTcpServer(uint32_t localSocketOption, uint32_t localRemoteIp, uint16_t localRemotePort,
    uint32_t localLocalIp, uint16_t localLocalPort, CaeServerService *pObject)
{
    int listenFd = 0;
    int reuse = 1;
    struct sockaddr_in strLocalAddr {};
    int flag = 1;

    this->SetStatus(SOCKET_STATUS_NORMAL);
    this->SetFd(-1);
    this->m_listenStatus = 1;
    this->m_threadId = ((pthread_t)(-1));

    this->m_serverService = pObject;
    SetEventNotice(nullptr);

    this->m_remoteIp = localRemoteIp;
    this->m_remotePort = localRemotePort;
    this->m_localIp = localLocalIp;
    this->m_localPort = localLocalPort;

    memset_s(&strLocalAddr, sizeof(strLocalAddr), 0, sizeof(strLocalAddr));
    strLocalAddr.sin_family = AF_INET;

    strLocalAddr.sin_addr.s_addr = htonl(localLocalIp);
    strLocalAddr.sin_port = htons(localLocalPort);
    this->m_socketOption = localSocketOption;

    listenFd = socket(PF_INET, SOCK_STREAM, 0);
    if (listenFd < 0) {
        LOGE("Create socket failed, errno: (%d): %s", errno, strerror(errno));
        return -1;
    } else {
        LOGI("Create socket %d successfully.", listenFd);
    }
    if (setsockopt(listenFd, SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse)) < 0) {
        close(listenFd);
        LOGE("Set socket opt: SO_REUSEPORT failed, errno: (%d): %s", errno, strerror(errno));
        return -1;
    }

    if (localSocketOption & SOCKET_OPTION_BITSET_NODELAY) {
        if (setsockopt(listenFd, IPPROTO_TCP, TCP_NODELAY, (const char *)&flag, sizeof(flag)) < 0) {
            close(listenFd);
            LOGE("Set socket opt: TCP_NODELAY failed errno: (%d): %s", errno, strerror(errno));
            return -1;
        }
    }

    if ((::bind(listenFd, (struct sockaddr *)&strLocalAddr, sizeof(strLocalAddr))) == -1) {
        close(listenFd);
        LOGE("Bind socket failed, errno: (%d) %s", errno, strerror(errno));
        return -1;
    }

    if (listen(listenFd, MAX_WAIT_QUEUE_LEN) != 0) {
        close(listenFd);
        LOGE("Listen socket failed, errno: (%d) %s", errno, strerror(errno));
        return -1;
    }

    this->m_fd = listenFd;
    this->m_status = SOCKET_STATUS_NORMAL;

    if (pthread_create((pthread_t *)&(this->m_threadId), nullptr, CaeTcpAcceptTaskEntry, (void *)(this)) != 0) {
        m_fd = -1;
        this->m_threadId = ((pthread_t)(-1));
        this->m_status = SOCKET_STATUS_ERROR;
        this->m_listenStatus = -1;
        close(listenFd);
        LOGE("Thread create failed, errno: (%d): %s", errno, strerror(errno));
        return -1;
    }
    (void)pthread_detach(this->m_threadId);
    LOGD("CaeCreateTcpServer end.");
    return 0;
}

CaeTcpServerSocket::CaeTcpServerSocket(uint32_t localLocalIp, uint16_t localLocalPort, CaeServerService *pObject)
{
    m_listenStatus = 0;
    m_threadId = 0;
    CaeCreateTcpServer(0, 0, 0, localLocalIp, localLocalPort, pObject);
}

CaeTcpServerSocket::CaeTcpServerSocket(uint32_t localSocketOption, uint32_t localLocalIp, uint16_t localLocalPort,
    CaeServerService *pObject)
{
    m_listenStatus = 0;
    m_threadId = 0;
    CaeCreateTcpServer(localSocketOption, 0, 0, localLocalIp, localLocalPort, pObject);
}

CaeTcpServerSocket::~CaeTcpServerSocket()
{
    m_serverService = nullptr;
    m_status = SOCKET_STATUS_ERROR;
    this->m_listenStatus = -1;

    if (-1 != m_fd) {
        shutdown(m_fd, SHUT_RDWR);
        close(m_fd);
    }

    while (((pthread_t)(-1)) != this->m_threadId) {
        usleep(10);
    }
    m_fd = -1;
}

void CaeTcpServerSocket::NotifyServerService(CaeSocket *caeSocket)
{
    if (m_serverService == nullptr) {
        return;
    }

    m_serverService->Callback(caeSocket);
}

void CaeTcpServerSocket::SetThreadId(pthread_t threadId)
{
    this->m_threadId = threadId;
}

int CaeTcpServerSocket::GetListenStatus() const
{
    return this->m_listenStatus;
}

void CaeTcpServerSocket::SetListenStatus(int status)
{
    this->m_listenStatus = status;
}

void CaeTcpServerSocket::SetServerType(ServerType type)
{
    this->m_serverType = type;
}

ServerType CaeTcpServerSocket::GetServerType()
{
    return this->m_serverType;
}
